补注喵
for Proj-02
FFmpeg
快去它的官网看看
FFmpeg 是一个十分 甚至九分 强大的开源媒体处理项目,包含若干个工具和库。本节里只用到了它的核心命令行工具。
尽管确实有 Python 库是封装它的库实现的,但是很多 Python 库还是通过封装命令行指令调用它的可执行文件来实现。
安装很简单,将下载的压缩包解压到一个纯英文目录后,将其中的 bin
文件夹的路径添加到环境变量 PATH
中即可。
添加路径到 PATH
什么原来你不知道如何将某个路径添加到 PATH
右击 此电脑
,点击 属性
,点击 高级系统设置
,点击 环境变量
截图中是 Windows10,但只要能找到高级系统设置就行,win11 也一样。通过搜索框里应该也能搜到?
自己决定是要在当前用户还是整个电脑范围内的 PATH 中添加这个路径,然后在用户变量和环境变量中找到 Path
,双击点开。
检查是否已经有了自己要添加的内容,没有的话再点击新建添加一项。
把路径贴进去,确定保存。
善用剪贴板喵
已经在运行的终端 / 软件可能需要重新打开以生效。
正则表达式
一种在文本中进行模式匹配和搜索的强大工具,使用特定的字符组合来描述或者定义一个搜索模式。在各个编程语言和工具中被广泛使用。
关于正则表达式的语法,参见菜鸟教程、MDN、Wikipedia、Google、Python 官方文档等等网站。
关于 Python 标准库中的正则表达式库 re
,参见官方文档、菜鸟教程等等网站。
虽然现在都说让 AI 帮写正则表达式,但是最基本的语法还是得会一点(
Python 中还有一些第三方库比如 regex
也能提供正则表达式操作,它们一般提供了某些方面的支持或优化。不过大部分时候用 re
就已经够了。
预编译正则表达式字符串为
Pattern
对象,能够在多次使用时节省大量时间。
特殊字符串标记
r""
原始字符串。在其中的\n
\t
等组合不会被解释为特殊字符,简直是为正则表达式量身打造的。u""
Unicode 字符串。Python3 中所有字符串都默认是 Unicode 字符串,无论使用u
标记与否。所以一般只用在 Python2 中。b""
字节字符串。显式包含在其中的只能是 ASCII 字符。f""
格式化字符串(f-string
)。在3.6+
版本中可用。是目前官方文档中列举的四种格式化方法中的一种。""""""
可跨行字符串。以一对三个引号包裹内容,可以与上述标记同时使用。常用于docstring
。谁再把这玩意说成是「注释」我就打爆谁的狗头
Python 中的字符串使用单双引号定义皆可。
装饰器
此部分参见 Decorator Playground
高级参数操作
定义函数时
任意数量参数
def func(*args:, **kwargs):
pass
2
*args
用于接收任意数量的位置参数, **kwargs
用于接收任意数量的关键字参数
在上述例子中, args
会成为一个元组(包含所有被接收的位置参数),而 kwargs
会成为一个字典(包含所有被接收的关键字参数)。
kwargs
的键的类型一定为str
这种接收任意数量的参数只是习惯性地被分别命名为 args
和 kwargs
,可以根据实际情况调整。
限制传参方式
在参数列表中加入单独的 /
以限制传参方式,此外 *
符也能限制传参方式。
在 /
前的参数是仅限位置参数,调用时只能按顺序传入
在 *
后的参数是仅限关键字参数,调用时必须指定参数名
多次在参数列表中使用
*
或**
属于语法错误。
/
则是在 3.8 版本加入的新特性。
来 Playground 玩一玩!
另外还记得吗?默认参数必须放在必需参数的后面。这是很重要的基础知识。
调用函数时
a = [1, 2, 3, 4]
b = {
"head": 114,
"body": 514,
"tail": 191,
}
func(*a, **b)
2
3
4
5
6
7
此时的 *
与 **
操作从「打包」变为「拆解」,它们将可迭代对象和字典分别拆成若干个位置参数和关键字参数。
所以就上述调用而言,等价于
func(1, 2, 3, 4, head=114, body=514, tail=191)
与定义时的行为相对应,调用时字典的键也必须是字符串。否则会报出
TypeError
。
JSONPath
用于查询 JSON 数据。跟用于查询 XML 的 XPath 差不多,语法偏简单,没有 XPath 那么多的内置函数。且提出时间较晚,仿照 XPath 设计的,没有官方标准。更适合简单的查询。
Python 中的支持库有 jsonpath-ng
、 jsonpath-rw
等。
类型标注
按标注对象分
对变量标注
a: int = 1
b: str = "<html></html>"
c: bytes = b"3"
session: requests.Session = requests.Session()
container: list[int] = []
2
3
4
5
实际上很多简单的变量根本用不着标注,
MyPy
这样的静态类型检查工具很容易推断出它们的值。你通常更需要标注的是像「某个接受了复杂且没标注的函数的返回值的变量」,和那些「在开始被赋值为空的容器」之类的,会导致后续推断失败的东西。
对函数标注
def func(x: int, y: Optional[float] = None, z: float | int = 0, c: complex = 1j) -> str:
return str(x) + str(y) + str(z) + str(c)
2
噢抱歉我真的很难举出一个独立但合理的例子
对函数的参数列表和返回值做标注即可。
按标注类型分
基本类型
int
/ bytes
/ float
/ complex
/ str
之类的
容器类型
从 typing
中导入同名但首字母大写的专门用于标注容器的对象,在其后使用中括号包括它的内容类型。
比如:
from typing import List, Tuple, Dict, Set
t: Tuple[int] = (1,) # 表示仅有一个 int 内容物的元组
t_: Tuple[int, ...] = (1, 2, 3) # 表示有若干个 int 内容物的元组
l: List[int] = [] # 表示一个可以容纳 int 类型的列表
d: Dict[str, int] = {} # 表示一个键为 str,值为 int 的字典
s: Set[int] = set() # 表示一个整数集合
2
3
4
5
6
7
8
9
10
在
3.9+
版本中,可以使用原生的容器代替typing
中的对象做标注比如
l: list[int] = []
特殊类型
下面所示都是 typing
中的对象
Union[T1, T2, ...]
表示既可以是T1
又可以是T2
等等(多个类型中的任意一种)在
3.10+
版本中,可以使用|
符直接分隔多个类型来替代Union
Optional[T]
表示既可以是None
又可以是T
(常用于可选参数)Any
表示任意类型Literal[CONST1, CONST2, ...]
用于标注变量只能为某些字面量此标注在
3.8+
版本中可用Callable[[ARGT1, ARGT2, ...], RET]
用于标注一个存放函数对象的变量TypeAlias
用于标注一个类型别名(它们会被等同处理)e.g.
_Params: TypeAlias = dict[str, str | int | bool]
不做标注直接赋值也被视为类型别名,在
3.12+
还可以使用type
语句(此处略去)NewType
用于从一个类型创建新的类型(检查器会将它们当成两个不同的类型)e.g.
A = NewType("A", int)
更多信息参见官方文档
类型检查的工作完全由第三方检查器负责,Python 解释器会忽略掉这些内容(但高版本的标注特性在低版本中仍然会引发语法错误)。添加类型标注只是为了使你的编写更加顺畅优雅,不是必须的。
pyi
文件
.pyi
文件是专门用来做类型标注的文件,一般与被标注的文件同名,位于同一目录下。
有些第三方库的代码本体和标注是分开的,比如正文中提到的 requests
和 types-requests
,后者就是前者的标注。很多时候如果忘了安装,mypy 会提醒你的。
判断「相等」
判断相等我们最常用到的是 ==
,但有时又会在一些地方看到 is
,怎么秽蚀呢
==
检查的是对象的值,而 is
检查的是「身份」(内存地址是否相同 / 是否是同一个对象)。就是这样。
>>> a = [1]
>>> b = a
>>> a == b, a is b
(True, True)
>>> b = a.copy()
>>> a == b, a is b
(True, False)
2
3
4
5
6
7
对于小整数、字符串这样的简单对象,
==
和is
的行为可能会表现得一致(内部优化导致的)。这是一个常见的导致误解的现象,需要注意。
比较特别地,判断一个变量是否是 None
,一般推荐使用 is
。因为:
None
是一个单例对象,在内存中只有一个实例,用is
更「Pythonic」。==
操作可以被重载(__eq__
),使用is
更准确、高效。
判断一个对象是否为 None
主要用在默认参数、检查初始化等地方。
要检查某个实例是否是由某个类(或它的子类)实例化产生的,可以使用
isinstance()
解包赋值
有的时候可以见到像这样的赋值方式:
>>> a, b = (0, 1)
>>> a
0
>>> b
1
2
3
4
5
这样的,在等号左边有多个变量,右边是一个包含相同数量元素的序列的赋值操作,叫做「解包赋值」。它会将序列中的元素按照顺序赋值到左边的变量上。
类似地,在很多地方也能看到这样的 for
循环:
d: dict = {}
for k, v in d.items():
...
2
3
叫做「迭代解包」,原理跟解包赋值一致。
在上面的这个例子中, dict.items()
方法返回一个包含了每一对键值的 dict_items
对象,其中每对键值以一个二元元组的形式存在。使用 for
将每个元组迭代出来,再用两个变量 k
v
去解包这个元组,等价于:
d: dict = {}
for pair in d.items():
k, v = pair
...
2
3
4
这种方法使得处理字典变得非常方便和直观。
另一个例子是os.walk()
的使用。可以自行前往了解。
推导式
推导式是一种强大且简洁的语法,适用于生成列表、字典、集合和生成器。
生成器表达式有时又被叫做元组推导式,因为它使用和元组一样的圆括号。但其实不存在元组推导式这个概念。但生成器表达式的语法与推导式的完全一致,因此就放在一起了。
推导式的括号内包含以下内容:一个初始表达式,后面为一个 for
子句,然后是零个或多个 for
或 if
子句。推导式中的初始表达式甚至可以是另一个推导式。
结果是由表达式依据 for
和 if
子句求值计算而得出一个新容器对象。(生成器表达式则是产生一个生成器,在对它迭代时才惰性地生成值)
一个拥有最简单的结构的列表推导式像这样:
# 生成由 0~9 中的每个数的平方组成的列表
# 推导式起始 推导式终止
# v v
sq = [x**2 for x in range(10)]
# ^^^^ ~~~~~~~~~~~~~~~~~~
# 表达式 for子句
2
3
4
5
6
对于集合推导式、集合推导式和生成器表达式,只需将圆括号换成对应种类的括号。
特别地,对于字典推导式,表达式部分应当由冒号 :
分成两个部分,分别代表键与值。同样一个简单的例子如下:
d: dict = {}
# 生成将字典d的键值对调形成的反转字典
# 推导式起始 推导式终止
# v v
d_ = {v: k for k, v in d.items()}
# ^^^^ ~~~~~~~~~~~~~~~~~~~~~
# 表达式 for子句
2
3
4
5
6
7
去官方文档看看更复杂的推导式吧,虽然不一定用得到就是了。
内置函数
参见官方文档 - 内置函数,也可以在菜鸟教程做简单查阅。
UNIX 时间戳
自 1970-01-01 00:00:00 UTC
以来的(毫)秒数。在 Python 中可使用 time.time()
获取。
更多关于时间的操作见官方文档 - time、官方文档 - datetime、官方文档 - timeit 等。